package org.gauth4me.core;

/**
 * Utility class: Google authentication secret key token decoder.
 */
public class TokenDecoder {
  private static final int INPUT_LENGTH = 16;
  private static final int TOKEN_LENGTH = 10;

  private TokenDecoder() {
    // no instantiation please
  }

  /**
   * Checks user input for validity.
   *
   * @param data the string to be checked
   * @return <b>true</b> if the string is valid
   */
  public static boolean isValid(String data) {
    data = correct(data);
    boolean result = false;
    if (data.length() == INPUT_LENGTH) {
      result = true;
      for (int i = 0; i < INPUT_LENGTH; ++i) {
        final char character = data.charAt(i);
        final boolean capitalASCIILetter = ((character >= 'A') && (character <= 'Z'));
        final boolean suitableDigit = ((character >= '2') && (character <= '7'));
        if (!(capitalASCIILetter || suitableDigit)) {
          result = false;
          break;
        }
      }
    }
    return result;
  }

  /**
   * Decodes an auth token 16-character base32-string to respective 10-byte array.
   *
   * @param data the string to be decoded
   * @return decoded byte stream
   * @throws IllegalArgumentException if the string does not contain valid auth token
   */
  static byte[] decode(String data) throws IllegalArgumentException {
    data = correct(data);
    if (!isValid(data)) {
      throw new IllegalArgumentException(data);
    }
    final byte[] bytes = unwrap(data);
    final byte[] result = new byte[TOKEN_LENGTH];
    for (int i = 0, j = 0; i < bytes.length; i += 8, j += 5) {
      result[j    ] = (byte) ((bytes[i    ] << 3) | (bytes[i + 1] >>> 2) & 0xFF);
      result[j + 1] = (byte) ((bytes[i + 1] << 6) | (bytes[i + 2] <<  1) | (bytes[i + 3] >>> 4) & 0xFF);
      result[j + 2] = (byte) ((bytes[i + 3] << 4) | (bytes[i + 4] >>> 1) & 0xFF);
      result[j + 3] = (byte) ((bytes[i + 4] << 7) | (bytes[i + 5] <<  2) | (bytes[i + 6] >>> 3) & 0xFF);
      result[j + 4] = (byte) ((bytes[i + 6] << 5) | (bytes[i + 7]      ) & 0xFF);
    }
    return result;
  }

  /**
   * Corrects a token string to standard form.
   * <p/>
   * Removes spaces, converts to uppercase, performs substitutions 0 → O, 1 → L.
   *
   * @param data the input string
   * @return corrected string
   */
  private static String correct(String data) {
    StringBuffer buffer = new StringBuffer();
    for (int i = 0; i < data.length(); ++i) {
      final char character = data.charAt(i);
      switch (character) {
        case ' ':
          continue;
        case '0':
          buffer.append('O');
          break;
        case '1':
          buffer.append('L');
          break;
        default:
          buffer.append(Character.toUpperCase(character));
      }
    }
    return buffer.toString();
  }

  /**
   * Converts base32 string into respective base32 code array.
   * <p/>
   * <tt>'A'</tt> – <tt>'Z'</tt> maps onto 0 – 25
   * <tt>'2'</tt> – <tt>'7'</tt> maps onto 26 – 31
   *
   * @param data the string to be converted
   * @return unwrapped data
   */
  private static byte[] unwrap(String data) {
    byte[] result = new byte[data.length()];
    for (int i = 0; i < data.length(); ++i) {
      if (data.charAt(i) < 'A') {
        result[i] = (byte) (data.charAt(i) - '2' + 26);
      }
      else {
        result[i] = (byte) (data.charAt(i) - 'A');
      }
    }
    return result;
  }
}
